package org.liu.simpleTank;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.EventObject;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;

public class Game {
	private UpdateContext updateContext;
	private Map map;
	private Renderer renderer;
	private RobotManager robotManager;
	// all elements are referenced here
	// in order to force them to be destroyed
	// when game ended
	private List<Element> elements;
	// a component is needed to create an image
	private Component comp;
	private Image bufimg;
	// active flag
	private boolean activeFlag;
	// indicates whether the environment is clean here
	// set to true after destroy and to false after init
	private boolean isClean = true;
	
	// key monitor, it is used to monitor user input
	KeyMonitor keyMonitor;
	{
		updateContext = new UpdateContext();
		updateContext.setReset();
		elements = new LinkedList<Element>();
		activeFlag = false;

		// create map to hold all Elements
		map = new Map();
		map.addAddedListener(new Map.AddedListener() { // listen on add event
			@Override
			public void added(Map.AddEvent e) {
				elements.add(e.getElement());
				// listen on remove event
				e.getElement().addElementListener(new Element.ElementListener() {
					@Override
					public void destroyed(EventObject e) {
						elements.remove((Element)e.getSource());
					}
				});
			}
		});
		// renderer
		renderer = new Renderer(this.map);
		// robotManager
		robotManager = new RobotManager(this);
		// keyMonitor
		keyMonitor = new KeyMonitor();
	}
	
	Tank player;
	
	public Game(Component comp) {
		this.comp = comp;
		bufimg = comp.createImage(CONSTS.SW, CONSTS.SH);

		// add inc life key F4
		comp.addKeyListener(new KeyAdapter() {
			@Override
			public void keyReleased(KeyEvent e) {
				if(e.getKeyCode() == KeyEvent.VK_F4)
					player.incLife();
			}
		});
	}
	
	public Map getMap() { return map; }
	
	/* game life cycle control
	 * init()
	 * destroy()
	 * start()
	 * stop()
	 */
	
	/**
	 * do everything needed to actually hold a map and place tanks 
	 * in their place and bind game logic (Home and so on)
	 */
	public void init() {
		// clean the game if it is not clean right now
		if(!isClean)
			return;
		isClean = false;
		
		// sample map
		map.initEnv(MapData.sample);

		// setup spawn points
		robotManager.setSpawnPoints();
		// add robots
		robotManager.setTankCount(10, 5, 5);
		robotManager.initialSpawn();
		
		// place a sample Tank
		player = new Tank(CONSTS.TANK_PLAYER1);
		player.setLife(5);
		keyMonitor.setControlStatus(player.getStatus());
		comp.addKeyListener(keyMonitor.keyAdapter);
		comp.addFocusListener(keyMonitor.focusAdapter);
		new FireManager(this, player);
		map.placeElement(24, 8, player);
	}

	/**
	 * to exit from a pause state
	 * make game accept update request
	 */
	public void start() {
		activeFlag = true;
		updateContext.setReset();
	}
	
	/**
	 * enter pause state
	 * ignore game update request
	 */
	public void stop() {
		activeFlag = false;
	}
	
	/**
	 * release all elements, only a clean map 
	 * and its cells remain here
	 */
	public void destroy() {
		activeFlag = false;
		synchronized (elements) {
			Element[] els = new Element[elements.size()];
			els = elements.toArray(els);
			elements.clear();
			for(int i = 0; i < els.length; i++)
				els[i].destroy();
		}
		robotManager.reset();
		isClean = true;
	}
	
	/**
	 * This is the method that make a game run.
	 * It should be invoked repeatedly. It updates
	 * data and renders new frame (which can be obtained
	 * by getFrame())
	 */
	public void update() {
		if(!activeFlag) // pause state
			return;
		// update updateContext
		long pTime = updateContext.getNow(); // old now
		long now = System.currentTimeMillis();
		updateContext.update(pTime, now, (int)(now - pTime));
		// update state
		synchronized (elements) {
			/* new element might be added to elements list,
			 * so iterator is not used here, instead a snapshot
			 * array of the current elements list is used to 
			 * traverse all elements prior to now.
			 */
			Element els[] = new Element[elements.size()];
			els = elements.toArray(els);
			for(int i = 0; i < els.length; i++)
				els[i].update(updateContext);
		}
		
		Graphics graphics = bufimg.getGraphics();
		renderer.render(graphics);
	}
	
	public Image getFrame() {
		return bufimg;
	}
	
	// methods related with Movable Element

	public Tank placeTank(int type, int row, int col, int dir) {
		Tank t = new Tank(type);
		t.direction = dir;
		map.placeElement(row, col, t);
		
		// attach fire manager
		new FireManager(this, t);
		
		return t;
	}
	
	public Bullet placeBullet(double speed, int row, int col, int dir) {
		Bullet b = new Bullet(speed, dir);
		map.placeElement(row, col, b);
		return b;
	}
}
